/*
 * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.corba.se.impl.ior;

import java.util.ListIterator;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;

import java.io.StringWriter;
import java.io.IOException;

import javax.rmi.CORBA.Util;

import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.OutputStream;

import org.omg.IOP.TAG_INTERNET_IOP;

import com.sun.corba.se.spi.ior.ObjectId;
import com.sun.corba.se.spi.ior.TaggedProfileTemplate;
import com.sun.corba.se.spi.ior.TaggedProfile;
import com.sun.corba.se.spi.ior.IOR;
import com.sun.corba.se.spi.ior.IORTemplate;
import com.sun.corba.se.spi.ior.IORTemplateList;
import com.sun.corba.se.spi.ior.IdentifiableFactoryFinder;
import com.sun.corba.se.spi.ior.IdentifiableContainerBase;
import com.sun.corba.se.spi.ior.ObjectKeyTemplate;
import com.sun.corba.se.spi.ior.IORFactories;

import com.sun.corba.se.spi.ior.iiop.GIOPVersion;

import com.sun.corba.se.spi.protocol.RequestDispatcherRegistry;

import com.sun.corba.se.spi.orb.ORB;

import com.sun.corba.se.spi.logging.CORBALogDomains;

import com.sun.corba.se.impl.encoding.MarshalOutputStream;

import com.sun.corba.se.impl.encoding.EncapsOutputStream;

import com.sun.corba.se.impl.orbutil.HexOutputStream;
import com.sun.corba.se.impl.orbutil.ORBConstants;

import com.sun.corba.se.impl.logging.IORSystemException;

// XXX remove this once getProfile is gone
import com.sun.corba.se.spi.ior.iiop.IIOPProfile;

/** An IOR is represented as a list of profiles.
* Only objects that extend TaggedProfile should be added to an IOR.
* However, enforcing this restriction requires overriding all
* of the addXXX methods inherited from List, so no check
* is included here.
* @author Ken Cavanaugh
*/
public class IORImpl extends IdentifiableContainerBase implements IOR {
    private String typeId;
    private ORB factory = null;
    private boolean isCachedHashValue = false;
    private int cachedHashValue;
    IORSystemException wrapper;

    public ORB getORB() {
        return factory;
    }

    /* This variable is set directly from the constructors that take
     * an IORTemplate or IORTemplateList as arguments; otherwise it
     * is derived from the list of TaggedProfile instances on the first
     * call to getIORTemplates.  Note that we assume that an IOR with
     * mutiple TaggedProfile instances has the same ObjectId in each
     * TaggedProfile, as otherwise the IOR could never be created through
     * an ObjectReferenceFactory.
     */
    private IORTemplateList iortemps = null;

    public boolean equals(Object obj) {
        if (obj == null)
            return false;

        if (!(obj instanceof IOR))
            return false;

        IOR other = (IOR) obj;

        return super.equals(obj) && typeId.equals(other.getTypeId());
    }

    public synchronized int hashCode() {
        if (!isCachedHashValue) {
            cachedHashValue = (super.hashCode() ^ typeId.hashCode());
            isCachedHashValue = true;
        }
        return cachedHashValue;
    }

    /** Construct an empty IOR.  This is needed for null object references.
    */
    public IORImpl(ORB orb) {
        this(orb, "");
    }

    public IORImpl(ORB orb, String typeid) {
        factory = orb;
        wrapper = IORSystemException.get(orb, CORBALogDomains.OA_IOR);
        this.typeId = typeid;
    }

    /** Construct an IOR from an IORTemplate by applying the same
    * object id to each TaggedProfileTemplate in the IORTemplate.
    */
    public IORImpl(ORB orb, String typeId, IORTemplate iortemp, ObjectId id) {
        this(orb, typeId);

        this.iortemps = IORFactories.makeIORTemplateList();
        this.iortemps.add(iortemp);

        addTaggedProfiles(iortemp, id);

        makeImmutable();
    }

    private void addTaggedProfiles(IORTemplate iortemp, ObjectId id) {
        ObjectKeyTemplate oktemp = iortemp.getObjectKeyTemplate();
        Iterator templateIterator = iortemp.iterator();

        while (templateIterator.hasNext()) {
            TaggedProfileTemplate ptemp = (TaggedProfileTemplate) (templateIterator.next());

            TaggedProfile profile = ptemp.create(oktemp, id);

            add(profile);
        }
    }

    /** Construct an IOR from an IORTemplate by applying the same
    * object id to each TaggedProfileTemplate in the IORTemplate.
    */
    public IORImpl(ORB orb, String typeId, IORTemplateList iortemps, ObjectId id) {
        this(orb, typeId);

        this.iortemps = iortemps;

        Iterator iter = iortemps.iterator();
        while (iter.hasNext()) {
            IORTemplate iortemp = (IORTemplate) (iter.next());
            addTaggedProfiles(iortemp, id);
        }

        makeImmutable();
    }

    public IORImpl(InputStream is) {
        this((ORB) (is.orb()), is.read_string());

        IdentifiableFactoryFinder finder = factory.getTaggedProfileFactoryFinder();

        EncapsulationUtility.readIdentifiableSequence(this, finder, is);

        makeImmutable();
    }

    public String getTypeId() {
        return typeId;
    }

    public void write(OutputStream os) {
        os.write_string(typeId);
        EncapsulationUtility.writeIdentifiableSequence(this, os);
    }

    public String stringify() {
        StringWriter bs;

        MarshalOutputStream s = sun.corba.OutputStreamFactory.newEncapsOutputStream(factory);
        s.putEndian();
        write((OutputStream) s);
        bs = new StringWriter();
        try {
            s.writeTo(new HexOutputStream(bs));
        } catch (IOException ex) {
            throw wrapper.stringifyWriteError(ex);
        }

        return ORBConstants.STRINGIFY_PREFIX + bs;
    }

    public synchronized void makeImmutable() {
        makeElementsImmutable();

        if (iortemps != null)
            iortemps.makeImmutable();

        super.makeImmutable();
    }

    public org.omg.IOP.IOR getIOPIOR() {
        EncapsOutputStream os = sun.corba.OutputStreamFactory.newEncapsOutputStream(factory);
        write(os);
        InputStream is = (InputStream) (os.create_input_stream());
        return org.omg.IOP.IORHelper.read(is);
    }

    public boolean isNil() {
        //
        // The check for typeId length of 0 below is commented out
        // as a workaround for a bug in ORBs which send a
        // null objref with a non-empty typeId string.
        //
        return ((size() == 0) /* && (typeId.length() == 0) */);
    }

    public boolean isEquivalent(IOR ior) {
        Iterator myIterator = iterator();
        Iterator otherIterator = ior.iterator();
        while (myIterator.hasNext() && otherIterator.hasNext()) {
            TaggedProfile myProfile = (TaggedProfile) (myIterator.next());
            TaggedProfile otherProfile = (TaggedProfile) (otherIterator.next());
            if (!myProfile.isEquivalent(otherProfile))
                return false;
        }

        return myIterator.hasNext() == otherIterator.hasNext();
    }

    private void initializeIORTemplateList() {
        // Maps ObjectKeyTemplate to IORTemplate
        Map oktempToIORTemplate = new HashMap();

        iortemps = IORFactories.makeIORTemplateList();
        Iterator iter = iterator();
        ObjectId oid = null; // used to check that all profiles have the same oid.
        while (iter.hasNext()) {
            TaggedProfile prof = (TaggedProfile) (iter.next());
            TaggedProfileTemplate ptemp = prof.getTaggedProfileTemplate();
            ObjectKeyTemplate oktemp = prof.getObjectKeyTemplate();

            // Check that all oids for all profiles are the same: if they are not,
            // throw exception.
            if (oid == null)
                oid = prof.getObjectId();
            else if (!oid.equals(prof.getObjectId()))
                throw wrapper.badOidInIorTemplateList();

            // Find or create the IORTemplate for oktemp.
            IORTemplate iortemp = (IORTemplate) (oktempToIORTemplate.get(oktemp));
            if (iortemp == null) {
                iortemp = IORFactories.makeIORTemplate(oktemp);
                oktempToIORTemplate.put(oktemp, iortemp);
                iortemps.add(iortemp);
            }

            iortemp.add(ptemp);
        }

        iortemps.makeImmutable();
    }

    /** Return the IORTemplateList for this IOR.  Will throw
     * exception if it is not possible to generate an IOR
     * from the IORTemplateList that is equal to this IOR,
     * which can only happen if not every TaggedProfile in the
     * IOR has the same ObjectId.
     */
    public synchronized IORTemplateList getIORTemplates() {
        if (iortemps == null)
            initializeIORTemplateList();

        return iortemps;
    }

    /** Return the first IIOPProfile in this IOR.
     * XXX THIS IS TEMPORARY FOR BACKWARDS COMPATIBILITY AND WILL BE REMOVED
     * SOON!
     */
    public IIOPProfile getProfile() {
        IIOPProfile iop = null;
        Iterator iter = iteratorById(TAG_INTERNET_IOP.value);
        if (iter.hasNext())
            iop = (IIOPProfile) (iter.next());

        if (iop != null)
            return iop;

        // if we come to this point then no IIOP Profile
        // is present.  Therefore, throw an exception.
        throw wrapper.iorMustHaveIiopProfile();
    }
}
